#include "xlcomm/net/poller/epoll_poller.h"

#include <assert.h>
#include <unistd.h>

#include "xlcomm/base/logger.h"
#include "xlcomm/net/detail/sockets_ops.h"

namespace xlcomm {
namespace net {

EPollPoller::EPollPoller(EventLoop* loop)
    : Poller(loop),
      epoll_fd_(::epoll_create1(EPOLL_CLOEXEC)),
      event_list_(kInitEventListSize) {
  LOG_INFO << "EPollPoller created, epoll_fd=" << epoll_fd_;
  XLCOMM_ASSERT(epoll_fd_ >= 0);
  XLCOMM_ASSERT(event_list_.size() > 0);
}

EPollPoller::~EPollPoller() { sockets::close(epoll_fd_); }

Timestamp EPollPoller::Poll(int timeout_ms, ChannelList* active_channels) {
  XLCOMM_ASSERT(loop_->IsInLoopThread());
  LOG_TRACE << "epoll_wait(epfd=" << epoll_fd_ << ")...";
  int readyn = ::epoll_wait(epoll_fd_, &event_list_[0],
                            static_cast<int>(event_list_.size()), timeout_ms);
  int save_errno = errno;
  Timestamp timestamp = DateTime::NowTimestamp();
  if (readyn > 0) {
    LOG_TRACE << "epoll_wait(epfd=" << epoll_fd_ << ") return " << readyn;
    FillActiveChannels(readyn, active_channels);
    if (readyn == static_cast<int>(event_list_.size()))
      event_list_.resize(event_list_.size() * 2);
  } else if (readyn == 0) {
    LOG_TRACE << "epoll_wait(epfd=" << epoll_fd_
              << ") return 0, nothing happends";
  } else {
    if (save_errno != EINTR) {
      errno = save_errno;
      LOG_SYSERROR(epoll_wait) << "epoll_fd:" << epoll_fd_;
    }
  }
  return timestamp;
}

void EPollPoller::FillActiveChannels(int readyn,
                                     ChannelList* active_channels) const {
  for (int i = 0; i < readyn; ++i) {
    Channel* channel = static_cast<Channel*>(event_list_[i].data.ptr);
    channel->set_triggered_events(event_list_[i].events);
    active_channels->push_back(channel);
  }
}

void EPollPoller::UpdateChannel(Channel* channel) {
  XLCOMM_ASSERT(loop_->IsInLoopThread());
  int fd = channel->fd();
  Channel::PollStatus status = channel->status();
  if (status == Channel::kStatusInit || status == Channel::kStatusDeleted) {
    // 加Status为了提高效率，因为查询map时间复杂度为O(logn)
    assert(!HasChannel(channel));
    EpollCtl(EPOLL_CTL_ADD, channel);
    channel->set_status(Channel::kStatusAdded);
    fd_channel_map_[fd] = channel;
  } else {
    assert(HasChannel(channel));
    if (channel->IsNoneEvent()) {
      EpollCtl(EPOLL_CTL_DEL, channel);
      channel->set_status(Channel::kStatusDeleted);
      fd_channel_map_.erase(fd);
    } else {
      EpollCtl(EPOLL_CTL_MOD, channel);
    }
  }
}

void EPollPoller::RemoveChannel(Channel* channel) {
  XLCOMM_ASSERT(loop_->IsInLoopThread());
  int fd = channel->fd();
  Channel::PollStatus status = channel->status();
  XLCOMM_ASSERT(status == Channel::kStatusAdded ||
                status == Channel::kStatusDeleted);
  XLCOMM_ASSERT(channel->IsNoneEvent());
  if (status == Channel::kStatusAdded) EpollCtl(EPOLL_CTL_DEL, channel);
  channel->set_status(Channel::kStatusInit);
  fd_channel_map_.erase(fd);
}

void EPollPoller::EpollCtl(int operation, Channel* channel) {
  XLCOMM_ASSERT(operation == EPOLL_CTL_ADD || operation == EPOLL_CTL_DEL ||
                operation == EPOLL_CTL_MOD);
  int fd = channel->fd();
  epoll_event event;
  MemZero(&event, sizeof event);
  event.events = channel->interested_events();
  event.data.ptr = channel;  // union类型，不能和event.data.fd = fd;共用
  LOG_TRACE << "epoll_ctl(op=" << OperationToString(operation) << " fd=" << fd
            << " event={" << channel->InterestedEventsToString() << "})";
  if (::epoll_ctl(epoll_fd_, operation, fd, &event) < 0) {
    LOG_SYSERROR(epoll_ctl)
        << "op=" << OperationToString(operation) << " fd=" << fd << " event={"
        << channel->InterestedEventsToString() << "}";
  }
}

const char* EPollPoller::OperationToString(int op) {
  switch (op) {
    case EPOLL_CTL_ADD:
      return "EPOLL_CTL_ADD";
    case EPOLL_CTL_DEL:
      return "EPOLL_CTL_DEL";
    case EPOLL_CTL_MOD:
      return "EPOLL_CTL_MOD";
    default:
      return "Unknown EPOLL_ Operation";
  }
}

}  // namespace net
}  // namespace xlcomm